/*
 *  Copyright 2007 Genoworks
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License. 
 */

package com.genoworks.retriever.bigdir;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.TreeSet;

class BigDiskVolume {

  private static final String DATA_NAME = "data"; //$NON-NLS-1$

  private File baseDir = null;

  private TreeMap<Integer, BigDiskFile> data = new TreeMap<Integer, BigDiskFile>();

  private int maxDataId = 0;

  private long maxSizeLimit = 0L;

  BigDiskVolume( File dir, long max ) {
    baseDir = dir;
    maxSizeLimit = max;
  }

  void add( BigDiskFile disk ) {
    data.put( disk.getId(), disk );
    maxDataId = Math.max( disk.getId(), maxDataId );
  }

  void close() throws IOException {
    for ( BigDiskFile file : data.values() ) {
      file.close();
    }
  }

  long getTotalSpace() throws IOException {
    synchronized( data ) {
      long result = 0L;
      for ( BigDiskFile file : data.values() ) {
        result += file.getTotalSpace();
      }
      return result;
    }
  }

  long getEmptySpace() {
    synchronized( data ) {
      long result = 0L;
      for ( BigDiskFile file : data.values() ) {
        result += file.getEmptySpace();
      }
      return result;
    }
  }

  BigFileSegment createNextSegment( int length ) throws FileNotFoundException {

    //
    for ( BigDiskFile file : data.values() ) {
      if ( file.getEmptySpace() > 0 ) {
        BigFileSegment result = file.createNextSegment( length );
        if ( result != null ) {
          return result;
        }
      }
    }

    /*
     * we have no more empty space on
     * existing data files, so create
     * another data file.
     */
    BigDiskFile file = createNextDiskFile();
    return file.createNextSegment( length );
  }

  TreeSet<BigFileSegment> write( TreeSet<BigFileSegment> segments,
      long position, byte[] buffer, int offset, int length ) throws IOException {

    //
    Iterator<BigFileSegment> segmentIterator = segments.iterator();
    while ( segmentIterator.hasNext() && length > 0 ) {

      //
      BigFileSegment seg = segmentIterator.next();

      //
      if ( position >= seg.getLength() ) {
        /*
         * fast forward to the
         * positioned segment.
         */
        position -= seg.getLength();
        continue;
      }

      //
      int writtenLength = write( seg, position, buffer, offset, length );
      offset += writtenLength;
      length -= writtenLength;
      position = 0;
    }

    //
    TreeSet<BigFileSegment> result = new TreeSet<BigFileSegment>();

    /*
     * we need more segments.
     */
    while ( length > 0 ) {

      //
      BigFileSegment seg = createNextSegment( length );

      //
      int writtenLength = write( seg, position, buffer, offset, length );
      offset += writtenLength;
      length -= writtenLength;

      //
      result.add( seg );
    }

    //
    return result;
  }

  BigDiskFile createNextDiskFile() throws FileNotFoundException {

    //
    int maxId = data.size() > 0 ? data.lastKey() + 1 : 1;

    //
    BigDiskFile result = new BigDiskFile( new File( baseDir,
        getDataFileName( maxId ) ), maxId, maxSizeLimit );

    //
    data.put( maxId, result );

    //
    return result;
  }

  int write( BigFileSegment segment, long position, byte[] buffer, int offset,
      int length ) throws IOException {

    //
    BigDiskFile file = data.get( segment.getSource() );

    //
    int small = (int) ( Math.min( length, segment.getLength() - position ) );

    //
    file.write( segment.getOffset() + position, buffer, offset, small );

    //
    return small;
  }

  void read( TreeSet<BigFileSegment> segments, long position, byte[] buffer,
      int offset, int length ) throws IOException {

    //
    long pos = position;
    boolean found = false;
    for ( BigFileSegment seg : segments ) {
      if ( found == false ) {

        // fast forward to the position
        if ( seg.getLength() < pos ) {
          pos -= seg.getLength();
        } else {
          found = true;
          if ( length > 0 ) {
            int len = read( seg, pos, buffer, offset, length );
            offset += len;
            length -= len;
          }
          pos = 0;
        }

      } else {

        // read from
        if ( length > 0 ) {
          int len = read( seg, pos, buffer, offset, length );
          offset += len;
          length -= len;
        }
      }
    }
  }

  private int read( BigFileSegment seg, long pos, byte[] buffer, int offset,
      int length ) throws IOException {

    //
    BigDiskFile file = data.get( seg.getSource() );

    //
    int len = (int) Math.min( seg.getLength() - pos, length );
    file.read( seg, pos, buffer, offset, len );

    //
    return len;
  }

  void delete( BigFile toDelete ) {
    for ( BigFileSegment seg : toDelete.getSegments() ) {
      BigDiskFile file = data.get( seg.getSource() );
      file.deleteSegment( seg );
    }
    toDelete.clearSegments();
  }

  String getDataFileName( int id ) {
    return DATA_NAME + id;
  }

  void readAllDiskFiles() {
    File[] dataFiles = baseDir.listFiles( new FilenameFilter() {

      public boolean accept( File arg0, String arg1 ) {
        if ( arg1.length() <= DATA_NAME.length() ) {
          return false;
        }
        char[] cs = arg1.substring( DATA_NAME.length() ).toCharArray();
        for ( char c : cs ) {
          if ( Character.isDigit( c ) == false ) {
            return false;
          }
        }
        return arg1.startsWith( DATA_NAME );
      }
    } );
    for ( File dataFile : dataFiles ) {
      int id = Integer.parseInt( dataFile.getName().substring( DATA_NAME.length() ) );
      try {
        add( new BigDiskFile( dataFile, id, maxSizeLimit ) );
      } catch( FileNotFoundException e ) {
      }
    }
  }

  void updateSegments( BigFile file ) {
    for ( BigFileSegment seg : file.getSegments() ) {
      data.get( seg.getSource() ).updateSegment( seg );
    }
  }

  public String toString() {
    String path = null;
    try {
      path = baseDir.getCanonicalPath();
    } catch( IOException e ) {
      path = baseDir.getAbsolutePath();
    }
    return "BigDiskVolume{dir=" + path
        + ",limit="
        + maxSizeLimit
        + ",files="
        + data.values()
        + "}";
  }
}
